package org.ptkparty.shared.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;

import org.datanucleus.api.jpa.annotations.Extension;
import org.ptkparty.server.dao.EMFService;
import org.ptkparty.shared.model.PtkEnum.GAME_TYPE;
import org.ptkparty.shared.model.PtkEnum.RESULT_TYPE;

import com.google.appengine.api.users.UserServiceFactory;

@Entity
public class GameEntity {

	private static final Logger LOGGER = Logger.getLogger(GameEntity.class.getName());

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
	private String id;

	@Version
	private Integer version;

	@OneToMany(fetch = FetchType.EAGER)
	@NotNull
	private List<String> teamOnePlayers;

	@OneToMany(fetch = FetchType.EAGER)
	@NotNull
	private List<String> teamTwoPlayers;

	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	private List<ResultEntity> results;

	private RESULT_TYPE resultType;

	private Date date;

	private String creator;

	private GAME_TYPE gameType;

	private String scale;

	private Boolean finished;

	@NotNull
	private Integer scoreTeamOne;

	private Double totTeamOne;

	@NotNull
	private Integer scoreTeamTwo;

	private Double totTeamTwo;

	@NotNull
	private String championship;

	@NotNull
	private String place;

	public static GameEntity findGameEntity(String id) {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			return em.find(GameEntity.class, id);
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
		return null;
	}

	public void init() {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			// Calculate the total score for each team
			totTeamOne = 0.0;
			for (String user : teamOnePlayers) {
				ResultEntity lastResult = ResultEntity.findLast(championship, user);

				if (lastResult == null) {
					lastResult = new ResultEntity();
					lastResult.setChampionship(championship);
					lastResult.setFannyLooseGames(0);
					lastResult.setFannyWinGames(0);
					lastResult.setGameNumber(0);
					lastResult.setLooseGames(0);
					lastResult.setPoints(100.0);
					lastResult.setUser(user);
					lastResult.setWinGames(0);
					this.addResult(lastResult);
				}

				totTeamOne += lastResult.getPoints();
			}

			totTeamTwo = 0.0;
			for (String user : teamTwoPlayers) {
				ResultEntity lastResult = ResultEntity.findLast(championship, user);

				if (lastResult == null) {
					lastResult = new ResultEntity();
					lastResult.setChampionship(championship);
					lastResult.setFannyLooseGames(0);
					lastResult.setFannyWinGames(0);
					lastResult.setGameNumber(0);
					lastResult.setLooseGames(0);
					lastResult.setPoints(100.0);
					lastResult.setUser(user);
					lastResult.setWinGames(0);
					this.addResult(lastResult);
				}

				totTeamTwo += lastResult.getPoints();
			}

			// Define the game type
			if (teamOnePlayers.size() == teamTwoPlayers.size()) {
				gameType = GAME_TYPE.OFFICIAL;
			} else {
				gameType = GAME_TYPE.UNOFFICIAL;
			}

			// Set creator
			creator = UserEntity.getUser(UserServiceFactory.getUserService().getCurrentUser().getEmail()).getId();

			// Get the correct scale
			ScaleEntity scale = ScaleEntity.getScale(Math.abs(totTeamOne - totTeamTwo));

			this.scale = scale.getId();

			setDate(new Date());

			finished = false;
			if (scoreTeamOne == null) {
				scoreTeamOne = 0;
			}
			if (scoreTeamTwo == null) {
				scoreTeamTwo = 0;
			}

			em.persist(this);
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
	}

	public void persist() {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			if (id == null) {
				em.persist(this);
			} else {
				em.merge(this);
			}
		} finally {
			em.close();
		}
	}

	public void createGame() {
		if (id == null) {
			init();
		}

		if (!finished) {
			EntityManager em = EMFService.get().createEntityManager();
			try {
				generateResults();
				em.merge(this);
			} catch (Exception e) {
				LOGGER.log(Level.SEVERE, e.getMessage(), e);
			} finally {
				em.close();
			}
		}
	}

	private void generateResults() {
		Map<String, ResultEntity> lastResuts = new HashMap<String, ResultEntity>();

		for (String user : teamOnePlayers) {
			lastResuts.put(user, ResultEntity.findLast(championship, user));
		}

		for (String user : teamTwoPlayers) {
			lastResuts.put(user, ResultEntity.findLast(championship, user));
		}

		// Set the victory type
		if (scoreTeamOne > scoreTeamTwo) {
			if (totTeamOne >= totTeamTwo) {
				resultType = RESULT_TYPE.NORMAL_WIN;
			} else {
				resultType = RESULT_TYPE.ANORMAL_WIN;
			}
		} else {
			if (totTeamOne >= totTeamTwo) {
				resultType = RESULT_TYPE.ANORMAL_WIN;
			} else {
				resultType = RESULT_TYPE.NORMAL_WIN;
			}
		}

		ScaleEntity scale = ScaleEntity.findScaleEntity(this.scale);

		for (String user : teamOnePlayers) {
			ResultEntity newResult = new ResultEntity(lastResuts.get(user));

			newResult.setGameNumber(newResult.getGameNumber() + 1);
			if (scoreTeamOne > scoreTeamTwo) {
				newResult.setWinGames(newResult.getWinGames() + 1);
				if (resultType == RESULT_TYPE.NORMAL_WIN) {
					newResult.setPoints(newResult.getPoints() + scale.getNormalWin());
				} else {
					newResult.setPoints(newResult.getPoints() + scale.getAnormalWin());
				}
				if (scoreTeamTwo == 0) {
					newResult.setFannyWinGames(newResult.getFannyWinGames() + 1);
					if (resultType == RESULT_TYPE.NORMAL_WIN) {
						newResult.setPoints(newResult.getPoints() + (scale.getNormalWin() * 0.5));
					} else {
						newResult.setPoints(newResult.getPoints() + (scale.getAnormalWin() * 0.5));
					}
				}
			} else {
				newResult.setLooseGames(newResult.getLooseGames() + 1);
				if (resultType == RESULT_TYPE.NORMAL_WIN) {
					newResult.setPoints(newResult.getPoints() + scale.getNormalLoose());
				} else {
					newResult.setPoints(newResult.getPoints() + scale.getAnormalLoose());
				}
				if (scoreTeamOne == 0) {
					newResult.setFannyLooseGames(newResult.getFannyLooseGames() + 1);
				}
			}
			this.addResult(newResult);
		}

		for (String user : teamTwoPlayers) {
			ResultEntity newResult = new ResultEntity(lastResuts.get(user));

			newResult.setGameNumber(newResult.getGameNumber() + 1);
			if (scoreTeamTwo > scoreTeamOne) {
				newResult.setWinGames(newResult.getWinGames() + 1);
				if (resultType == RESULT_TYPE.NORMAL_WIN) {
					newResult.setPoints(newResult.getPoints() + scale.getNormalWin());
				} else {
					newResult.setPoints(newResult.getPoints() + scale.getAnormalWin());
				}
				if (scoreTeamOne == 0) {
					newResult.setFannyWinGames(newResult.getFannyWinGames() + 1);
					if (resultType == RESULT_TYPE.NORMAL_WIN) {
						newResult.setPoints(newResult.getPoints() + (scale.getNormalWin() * 0.5));
					} else {
						newResult.setPoints(newResult.getPoints() + (scale.getAnormalWin() * 0.5));
					}
				}
			} else {
				newResult.setLooseGames(newResult.getLooseGames() + 1);
				if (resultType == RESULT_TYPE.NORMAL_WIN) {
					newResult.setPoints(newResult.getPoints() + scale.getNormalLoose());
				} else {
					newResult.setPoints(newResult.getPoints() + scale.getAnormalLoose());
				}
				if (scoreTeamTwo == 0) {
					newResult.setFannyLooseGames(newResult.getFannyLooseGames() + 1);
				}
			}
			this.addResult(newResult);
		}

		if (date == null) {
			date = new Date();
		}

		finished = true;
	}

	private void addResult(ResultEntity newResult) {
		if (results == null) {
			results = new ArrayList<ResultEntity>();
		}
		results.add(newResult);
	}

	public Integer getVersion() {
		return version;
	}

	public void setVersion(Integer version) {
		this.version = version;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	public Integer getScoreTeamOne() {
		return scoreTeamOne;
	}

	public void setScoreTeamOne(Integer scoreTeamOne) {
		this.scoreTeamOne = scoreTeamOne;
	}

	public Integer getScoreTeamTwo() {
		return scoreTeamTwo;
	}

	public void setScoreTeamTwo(Integer scoreTeamTwo) {
		this.scoreTeamTwo = scoreTeamTwo;
	}

	public String getChampionship() {
		return championship;
	}

	public void setChampionship(String championship) {
		this.championship = championship;
	}

	public List<String> getTeamOnePlayers() {
		return teamOnePlayers;
	}

	public void setTeamOnePlayers(List<String> teamOnePlayers) {
		this.teamOnePlayers = teamOnePlayers;
	}

	public List<String> getTeamTwoPlayers() {
		return teamTwoPlayers;
	}

	public void setTeamTwoPlayers(List<String> teamTwoPlayers) {
		this.teamTwoPlayers = teamTwoPlayers;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPlace() {
		return place;
	}

	public void setPlace(String place) {
		this.place = place;
	}

	public RESULT_TYPE getResultType() {
		return resultType;
	}

	public void setResultType(RESULT_TYPE resultType) {
		this.resultType = resultType;
	}

	public GAME_TYPE getGameType() {
		return gameType;
	}

	public void setGameType(GAME_TYPE gameType) {
		this.gameType = gameType;
	}

	public String getScale() {
		return scale;
	}

	public void setScale(String scale) {
		this.scale = scale;
	}

	public String getCreator() {
		return creator;
	}

	public void setCreator(String creator) {
		this.creator = creator;
	}

	public List<ResultEntity> getResults() {
		return results;
	}

	public void setResults(List<ResultEntity> results) {
		this.results = results;
	}

	public static List<GameEntity> getAllFinishedGamesForChampionship(String championshipId) {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			return em.createQuery("SELECT g FROM GameEntity g WHERE g.championship=:championship and g.finished=true order by date", GameEntity.class) //
					.setParameter("championship", championshipId) //
					.getResultList();
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
		return null;
	}

	public static List<GameEntity> getAllGamesForChampionshipOrdered(String championshipId) {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			List<GameEntity> result = em.createQuery("SELECT g FROM GameEntity g WHERE g.championship=:championship order by date", GameEntity.class) //
					.setParameter("championship", championshipId) //
					.getResultList();

			return result;
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
		return null;
	}

	public Boolean getFinished() {
		return finished;
	}

	public void setFinished(Boolean finished) {
		this.finished = finished;
	}

	public void addTeamOnePlayer(String player) {
		if (teamOnePlayers == null) {
			teamOnePlayers = new ArrayList<String>();
		}
		teamOnePlayers.add(player);
	}

	public void addTeamTwoPlayer(String player) {
		if (teamTwoPlayers == null) {
			teamTwoPlayers = new ArrayList<String>();
		}
		teamTwoPlayers.add(player);
	}

	public static void delete(String id) {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			em.getTransaction().begin();
			GameEntity ent = em.getReference(GameEntity.class, id);
			if (ent != null) {
				em.remove(ent);
			}
			em.getTransaction().commit();
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			em.getTransaction().rollback();
		} finally {
			em.close();
		}
	}

	@SuppressWarnings("unchecked")
	public static void regenerateResults(String championshipId) {
		EntityManager em = EMFService.get().createEntityManager();
		try {
			List<GameEntity> games = em.createQuery("SELECT g FROM GameEntity g WHERE g.championship=:championship order by date") //
					.setParameter("championship", championshipId) //
					.getResultList();

			// Delete results
			for (GameEntity game : games) {
				game.setFinished(false);
				game.getResults().clear();
				em.merge(game);
			}

			// Generate results
			for (GameEntity game : games) {
				game.generateResults();
				em.merge(game);
			}

		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		} finally {
			em.close();
		}
	}
}
